package com.dhcc;

/**
 * 分布式 ID 生成器，基于 Twitter 的 Snowflake 算法实现。
 * <p>
 * 该类生成的 ID 是一个 64 位的长整型数字，结构如下：
 * </p>
 * <pre>
 *  0 - 41位时间戳 - 5位数据中心ID - 5位机器ID - 12位序列号
 * </pre>
 * <ul>
 *   <li>1 位符号位，始终为 0。</li>
 *   <li>41 位时间戳，精确到毫秒，表示当前时间与自定义纪元（twepoch）的差值。</li>
 *   <li>5 位数据中心 ID，标识数据中心（机房）。</li>
 *   <li>5 位机器 ID，标识同一数据中心内的不同机器。</li>
 *   <li>12 位序列号，用于同一毫秒内生成多个 ID。</li>
 * </ul>
 *
 * <p>
 * 注意：该类不是线程安全的，需要确保在并发环境下正确使用，例如通过 {@code synchronized} 关键字。
 * </p>
 * @ProjectName Dhcc-Cloud
 * @PackageName com.dhcc
 * @Title SnowflakeIdWorker
 * @Date 2024/10/15 上午9:53
 * @Author LiuGuoting
 */
public class SnowflakeIdWorker {

    /**
     * 自定义纪元时间戳（2024-01-01），单位：毫秒。
     */
    private final long twepoch = 1704067200000L;

    /**
     * 机器 ID 所占的位数。
     */
    private final long workerIdBits = 5L;

    /**
     * 数据中心 ID 所占的位数。
     */
    private final long datacenterIdBits = 5L;

    /**
     * 序列号所占的位数。
     */
    private final long sequenceBits = 12L;

    /**
     * 支持的最大机器 ID，结果是 31（这个位移算法可以快速计算出最大值）。
     */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

    /**
     * 支持的最大数据中心 ID，结果是 31。
     */
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

    /**
     * 机器 ID 左移位数（12位）。
     */
    private final long workerIdShift = sequenceBits;

    /**
     * 数据中心 ID 左移位数（12 + 5 位）。
     */
    private final long datacenterIdShift = sequenceBits + workerIdBits;

    /**
     * 时间戳左移位数（12 + 5 + 5 位）。
     */
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;

    /**
     * 生成序列的掩码，这里为 4095（0b111111111111=4095）。
     */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    /**
     * 工作机器 ID（0~31）。
     */
    private long workerId;

    /**
     * 数据中心 ID（0~31）。
     */
    private long datacenterId;

    /**
     * 毫秒内序列（0~4095）。
     */
    private long sequence = 0L;

    /**
     * 上次生成 ID 的时间戳。
     */
    private long lastTimestamp = -1L;

    /**
     * 构造函数。
     *
     * @param workerId     工作 ID（0~31）
     * @param datacenterId 数据中心 ID（0~31）
     * @throws IllegalArgumentException 如果 {@code workerId} 或 {@code datacenterId} 超出范围
     */
    public SnowflakeIdWorker(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format(
                    "工作 ID（workerId）不能大于 %d 或小于 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format(
                    "数据中心 ID（datacenterId）不能大于 %d 或小于 0", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }


    /**
     * 生成下一个唯一 ID。
     *
     * @return 返回生成的 64 位 ID
     * @throws RuntimeException 如果当前时间小于上一次生成 ID 的时间戳，表示系统时钟回拨
     */
    public synchronized long nextId() {
        long timestamp = timeGen();

        // 如果当前时间小于上次生成 ID 的时间戳，说明系统时钟回退过
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format(
                    "时钟发生回退，拒绝在 %d 毫秒内生成 ID",
                    lastTimestamp - timestamp));
        }


        // 如果是在同一毫秒内生成的，则进行序列号递增
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            // 毫秒内序列溢出
            if (sequence == 0) {
                // 阻塞到下一毫秒，获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            // 时间戳改变，序列号重置
            sequence = 0L;
        }

        // 更新上次生成 ID 的时间戳
        lastTimestamp = timestamp;

        // 组合各部分生成最终的 ID，并返回
        return ((timestamp - twepoch) << timestampLeftShift)
                | (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift)
                | sequence;
    }

    /**
     * 阻塞到下一个毫秒，直到获得新的时间戳。
     *
     * @param lastTimestamp 上次生成 ID 的时间戳
     * @return 返回当前时间戳
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        // 循环直到获得新的时间戳
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间。
     *
     * @return 当前时间（毫秒）
     */
    protected long timeGen() {
        return System.currentTimeMillis();
    }
}
